home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 March / PCWorld_2008-03_cd.bin / v cisle / mobiDVD / MobiDVD-1.0.0.6.exe / xulrunner / components / nsLivemarkService.js < prev    next >
Text File  |  2007-07-27  |  33KB  |  1,005 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  * ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License
  6.  * Version 1.1 (the "License"); you may not use this file except in
  7.  * compliance with the License. You may obtain a copy of the License
  8.  * at http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS"
  11.  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  12.  * the License for the specific language governing rights and
  13.  * limitations under the License.
  14.  *
  15.  * The Original Code is the Places JS Livemark Service.
  16.  *
  17.  * The Initial Developer of the Original Code is Mozilla Corporation.
  18.  * Portions created by the Initial Developer are Copyright (C) 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Annie Sullivan <annie.sullivan@gmail.com> (C++ author)
  23.  *   Joe Hughes <joe@retrovirus.com>
  24.  *   Vladimir Vukicevic <vladimir@pobox.com>
  25.  *   Masayuki Nakano <masayuki@d-toybox.com>
  26.  *   Robert Sayre <sayrer@gmail.com> (JS port)
  27.  *
  28.  * Alternatively, the contents of this file may be used under the
  29.  * terms of either the GNU General Public License Version 2 or later
  30.  * (the "GPL"), or the GNU Lesser General Public License Version 2.1
  31.  * or later (the "LGPL"), in which case the provisions of the GPL or
  32.  * the LGPL are applicable instead of those above. If you wish to
  33.  * allow use of your version of this file only under the terms of
  34.  * either the GPL or the LGPL, and not to allow others to use your
  35.  * version of this file under the terms of the MPL, indicate your
  36.  * decision by deleting the provisions above and replace them with the
  37.  * notice and other provisions required by the GPL or the LGPL. If you
  38.  * do not delete the provisions above, a recipient may use your
  39.  * version of this file under the terms of any one of the MPL, the GPL
  40.  * or the LGPL.
  41.  *
  42.  * ***** END LICENSE BLOCK ***** */
  43.  
  44. const Cc = Components.classes;
  45. const Ci = Components.interfaces;
  46. const Cr = Components.results;
  47.  
  48. //@line 36 "/cygdrive/d/builds/tinderbox/XR-Trunk/WINNT_5.2_Depend/mozilla/toolkit/components/url-classifier/content/moz/lang.js"
  49.  
  50.  
  51. /**
  52.  * lang.js - Some missing JavaScript language features
  53.  */
  54.  
  55. /**
  56.  * Partially applies a function to a particular "this object" and zero or
  57.  * more arguments. The result is a new function with some arguments of the first
  58.  * function pre-filled and the value of |this| "pre-specified".
  59.  *
  60.  * Remaining arguments specified at call-time are appended to the pre-
  61.  * specified ones.
  62.  *
  63.  * Usage:
  64.  * var barMethBound = BindToObject(myFunction, myObj, "arg1", "arg2");
  65.  * barMethBound("arg3", "arg4");
  66.  *
  67.  * @param fn {string} Reference to the function to be bound
  68.  *
  69.  * @param self {object} Specifies the object which |this| should point to
  70.  * when the function is run. If the value is null or undefined, it will default
  71.  * to the global object.
  72.  *
  73.  * @returns {function} A partially-applied form of the speficied function.
  74.  */
  75. function BindToObject(fn, self, opt_args) {
  76.   var boundargs = fn.boundArgs_ || [];
  77.   boundargs = boundargs.concat(Array.slice(arguments, 2, arguments.length));
  78.  
  79.   if (fn.boundSelf_)
  80.     self = fn.boundSelf_;
  81.   if (fn.boundFn_)
  82.     fn = fn.boundFn_;
  83.  
  84.   var newfn = function() {
  85.     // Combine the static args and the new args into one big array
  86.     var args = boundargs.concat(Array.slice(arguments));
  87.     return fn.apply(self, args);
  88.   }
  89.  
  90.   newfn.boundArgs_ = boundargs;
  91.   newfn.boundSelf_ = self;
  92.   newfn.boundFn_ = fn;
  93.  
  94.   return newfn;
  95. }
  96.  
  97. /**
  98.  * Inherit the prototype methods from one constructor into another.
  99.  *
  100.  * Usage:
  101.  *
  102.  * function ParentClass(a, b) { }
  103.  * ParentClass.prototype.foo = function(a) { }
  104.  *
  105.  * function ChildClass(a, b, c) {
  106.  *   ParentClass.call(this, a, b);
  107.  * }
  108.  *
  109.  * ChildClass.inherits(ParentClass);
  110.  *
  111.  * var child = new ChildClass("a", "b", "see");
  112.  * child.foo(); // works
  113.  *
  114.  * In addition, a superclass' implementation of a method can be invoked
  115.  * as follows:
  116.  *
  117.  * ChildClass.prototype.foo = function(a) {
  118.  *   ChildClass.superClass_.foo.call(this, a);
  119.  *   // other code
  120.  * };
  121.  */
  122. Function.prototype.inherits = function(parentCtor) {
  123.   var tempCtor = function(){};
  124.   tempCtor.prototype = parentCtor.prototype;
  125.   this.superClass_ = parentCtor.prototype;
  126.   this.prototype = new tempCtor();
  127. }
  128. //@line 36 "/cygdrive/d/builds/tinderbox/XR-Trunk/WINNT_5.2_Depend/mozilla/toolkit/components/url-classifier/content/moz/observer.js"
  129.  
  130.  
  131. // A couple of classes to simplify creating observers. 
  132. //
  133. // // Example1:
  134. //
  135. // function doSomething() { ... }
  136. // var observer = new G_ObserverWrapper(topic, doSomething);
  137. // someObj.addObserver(topic, observer);
  138. //
  139. // // Example2: 
  140. //
  141. // function doSomething() { ... }
  142. // new G_ObserverServiceObserver("profile-after-change", 
  143. //                               doSomething,
  144. //                               true /* run only once */);
  145.  
  146.  
  147. /**
  148.  * This class abstracts the admittedly simple boilerplate required of
  149.  * an nsIObserver. It saves you the trouble of implementing the
  150.  * indirection of your own observe() function.
  151.  *
  152.  * @param topic String containing the topic the observer will filter for
  153.  *
  154.  * @param observeFunction Reference to the function to call when the 
  155.  *                        observer fires
  156.  *
  157.  * @constructor
  158.  */
  159. function G_ObserverWrapper(topic, observeFunction) {
  160.   this.debugZone = "observer";
  161.   this.topic_ = topic;
  162.   this.observeFunction_ = observeFunction;
  163. }
  164.  
  165. /**
  166.  * XPCOM
  167.  */
  168. G_ObserverWrapper.prototype.QueryInterface = function(iid) {
  169.   if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserver))
  170.     return this;
  171.   throw Components.results.NS_ERROR_NO_INTERFACE;
  172. }
  173.  
  174. /**
  175.  * Invoked by the thingy being observed
  176.  */
  177. G_ObserverWrapper.prototype.observe = function(subject, topic, data) {
  178.   if (topic == this.topic_)
  179.     this.observeFunction_(subject, topic, data);
  180. }
  181.  
  182.  
  183. /**
  184.  * This class abstracts the admittedly simple boilerplate required of
  185.  * observing an observerservice topic. It implements the indirection
  186.  * required, and automatically registers to hear the topic.
  187.  *
  188.  * @param topic String containing the topic the observer will filter for
  189.  *
  190.  * @param observeFunction Reference to the function to call when the 
  191.  *                        observer fires
  192.  *
  193.  * @param opt_onlyOnce Boolean indicating if the observer should unregister
  194.  *                     after it has fired
  195.  *
  196.  * @constructor
  197.  */
  198. function G_ObserverServiceObserver(topic, observeFunction, opt_onlyOnce) {
  199.   this.debugZone = "observerserviceobserver";
  200.   this.topic_ = topic;
  201.   this.observeFunction_ = observeFunction;
  202.   this.onlyOnce_ = !!opt_onlyOnce;
  203.   
  204.   this.observer_ = new G_ObserverWrapper(this.topic_, 
  205.                                          BindToObject(this.observe_, this));
  206.   this.observerService_ = Cc["@mozilla.org/observer-service;1"]
  207.                           .getService(Ci.nsIObserverService);
  208.   this.observerService_.addObserver(this.observer_, this.topic_, false);
  209. }
  210.  
  211. /**
  212.  * Unregister the observer from the observerservice
  213.  */
  214. G_ObserverServiceObserver.prototype.unregister = function() {
  215.   this.observerService_.removeObserver(this.observer_, this.topic_);
  216.   this.observerService_ = null;
  217. }
  218.  
  219. /**
  220.  * Invoked by the observerservice
  221.  */
  222. G_ObserverServiceObserver.prototype.observe_ = function(subject, topic, data) {
  223.   this.observeFunction_(subject, topic, data);
  224.   if (this.onlyOnce_)
  225.     this.unregister();
  226. }
  227.  
  228. //@line 36 "/cygdrive/d/builds/tinderbox/XR-Trunk/WINNT_5.2_Depend/mozilla/toolkit/components/url-classifier/content/moz/alarm.js"
  229.  
  230.  
  231. // An Alarm fires a callback after a certain amount of time, or at
  232. // regular intervals. It's a convenient replacement for
  233. // setTimeout/Interval when you don't want to bind to a specific
  234. // window.
  235. //
  236. // The ConditionalAlarm is an Alarm that cancels itself if its callback 
  237. // returns a value that type-converts to true.
  238. //
  239. // Example:
  240. //
  241. //  function foo() { dump('hi'); };
  242. //  new G_Alarm(foo, 10*1000);                   // Fire foo in 10 seconds
  243. //  new G_Alarm(foo, 10*1000, true /*repeat*/);  // Fire foo every 10 seconds
  244. //  new G_Alarm(foo, 10*1000, true, 7);          // Fire foo every 10 seconds
  245. //                                               // seven times
  246. //  new G_ConditionalAlarm(foo, 1000, true); // Fire every sec until foo()==true
  247. //
  248. //  // Fire foo every 10 seconds until foo returns true or until it fires seven
  249. //  // times, whichever happens first.
  250. //  new G_ConditionalAlarm(foo, 10*1000, true /*repeating*/, 7);
  251. //
  252. // TODO: maybe pass an isFinal flag to the callback if they opted to
  253. // set maxTimes and this is the last iteration?
  254.  
  255.  
  256. /**
  257.  * Set an alarm to fire after a given amount of time, or at specific 
  258.  * intervals.
  259.  *
  260.  * @param callback Function to call when the alarm fires
  261.  * @param delayMS Number indicating the length of the alarm period in ms
  262.  * @param opt_repeating Boolean indicating whether this should fire 
  263.  *                      periodically
  264.  * @param opt_maxTimes Number indicating a maximum number of times to 
  265.  *                     repeat (obviously only useful when opt_repeating==true)
  266.  */
  267. function G_Alarm(callback, delayMS, opt_repeating, opt_maxTimes) {
  268.   this.debugZone = "alarm";
  269.   this.callback_ = callback;
  270.   this.repeating_ = !!opt_repeating;
  271.   this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  272.   var type = opt_repeating ? 
  273.              this.timer_.TYPE_REPEATING_SLACK : 
  274.              this.timer_.TYPE_ONE_SHOT;
  275.   this.maxTimes_ = opt_maxTimes ? opt_maxTimes : null;
  276.   this.nTimes_ = 0;
  277.  
  278.   this.observerServiceObserver_ = new G_ObserverServiceObserver(
  279.                                         'xpcom-shutdown',
  280.                                         BindToObject(this.cancel, this));
  281.  
  282.   // Ask the timer to use nsITimerCallback (.notify()) when ready
  283.   this.timer_.initWithCallback(this, delayMS, type);
  284. }
  285.  
  286. /**
  287.  * Cancel this timer 
  288.  */
  289. G_Alarm.prototype.cancel = function() {
  290.   if (!this.timer_) {
  291.     return;
  292.   }
  293.  
  294.   this.timer_.cancel();
  295.   // Break circular reference created between this.timer_ and the G_Alarm
  296.   // instance (this)
  297.   this.timer_ = null;
  298.   this.callback_ = null;
  299.  
  300.   // We don't need the shutdown observer anymore
  301.   this.observerServiceObserver_.unregister();
  302. }
  303.  
  304. /**
  305.  * Invoked by the timer when it fires
  306.  * 
  307.  * @param timer Reference to the nsITimer which fired (not currently 
  308.  *              passed along)
  309.  */
  310. G_Alarm.prototype.notify = function(timer) {
  311.   // fire callback and save results
  312.   var ret = this.callback_();
  313.   
  314.   // If they've given us a max number of times to fire, enforce it
  315.   this.nTimes_++;
  316.   if (this.repeating_ && 
  317.       typeof this.maxTimes_ == "number" 
  318.       && this.nTimes_ >= this.maxTimes_) {
  319.     this.cancel();
  320.   } else if (!this.repeating_) {
  321.     // Clear out the callback closure for TYPE_ONE_SHOT timers
  322.     this.cancel();
  323.   }
  324.   // We don't cancel/cleanup timers that repeat forever until either
  325.   // xpcom-shutdown occurs or cancel() is called explicitly.
  326.  
  327.   return ret;
  328. }
  329.  
  330. G_Alarm.prototype.setDelay = function(delay) {
  331.   this.timer_.delay = delay;
  332. }
  333.  
  334. /**
  335.  * XPCOM cruft
  336.  */
  337. G_Alarm.prototype.QueryInterface = function(iid) {
  338.   if (iid.equals(Components.interfaces.nsISupports) ||
  339.       iid.equals(Components.interfaces.nsITimerCallback))
  340.     return this;
  341.  
  342.   throw Components.results.NS_ERROR_NO_INTERFACE;
  343. }
  344.  
  345.  
  346. /**
  347.  * An alarm with the additional property that it cancels itself if its 
  348.  * callback returns true.
  349.  *
  350.  * For parameter documentation, see G_Alarm
  351.  */
  352. function G_ConditionalAlarm(callback, delayMS, opt_repeating, opt_maxTimes) {
  353.   G_Alarm.call(this, callback, delayMS, opt_repeating, opt_maxTimes);
  354.   this.debugZone = "conditionalalarm";
  355. }
  356.  
  357. G_ConditionalAlarm.inherits(G_Alarm);
  358.  
  359. /**
  360.  * Invoked by the timer when it fires
  361.  * 
  362.  * @param timer Reference to the nsITimer which fired (not currently 
  363.  *              passed along)
  364.  */
  365. G_ConditionalAlarm.prototype.notify = function(timer) {
  366.   // Call G_Alarm::notify
  367.   var rv = G_Alarm.prototype.notify.call(this, timer);
  368.  
  369.   if (this.repeating_ && rv) {
  370.     G_Debug(this, "Callback of a repeating alarm returned true; cancelling.");
  371.     this.cancel();
  372.   }
  373. }
  374. //@line 51 "/cygdrive/d/builds/tinderbox/XR-Trunk/WINNT_5.2_Depend/mozilla/toolkit/components/places/src/nsLivemarkService.js"
  375.  
  376. function LOG(str) {
  377.   dump("*** " + str + "\n");
  378. }
  379.  
  380. const LS_CLASSID = Components.ID("{dca61eb5-c7cd-4df1-b0fb-d0722baba251}");
  381. const LS_CLASSNAME = "Livemark Service";
  382. const LS_CONTRACTID = "@mozilla.org/browser/livemark-service;2";
  383.  
  384. const LIVEMARK_TIMEOUT = 15000; // fire every 15 seconds
  385. const LIVEMARK_ICON_URI = "chrome://browser/skin/places/livemarkItem.png";
  386. const PLACES_BUNDLE_URI = 
  387.   "chrome://browser/locale/places/places.properties";
  388. const DEFAULT_LOAD_MSG = "Live Bookmark loading...";
  389. const DEFAULT_FAIL_MSG = "Live Bookmark feed failed to load.";
  390. const LMANNO_FEEDURI = "livemark/feedURI";
  391. const LMANNO_SITEURI = "livemark/siteURI";
  392. const LMANNO_EXPIRATION = "livemark/expiration";
  393. const LMANNO_BMANNO = "livemark/bookmarkFeedURI";
  394.  
  395. const PS_CONTRACTID = "@mozilla.org/preferences-service;1";
  396. const NH_CONTRACTID = "@mozilla.org/browser/nav-history-service;1";
  397. const AS_CONTRACTID = "@mozilla.org/browser/annotation-service;1";
  398. const OS_CONTRACTID = "@mozilla.org/observer-service;1";
  399. const SB_CONTRACTID = "@mozilla.org/intl/stringbundle;1";
  400. const IO_CONTRACTID = "@mozilla.org/network/io-service;1";
  401. const BMS_CONTRACTID = "@mozilla.org/browser/nav-bookmarks-service;1";
  402. const FAV_CONTRACTID = "@mozilla.org/browser/favicon-service;1";
  403. const LG_CONTRACTID = "@mozilla.org/network/load-group;1";
  404. const FP_CONTRACTID = "@mozilla.org/feed-processor;1";
  405. const SEC_CONTRACTID = "@mozilla.org/scriptsecuritymanager;1";
  406. const SEC_FLAGS = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
  407.  
  408. // Check every hour by default
  409. var gExpiration = 3600000;
  410.  
  411. // Check every 10 minutes on error
  412. const ERROR_EXPIRATION = 600000;
  413.  
  414. var gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService);
  415. var gStringBundle;
  416. function GetString(name)
  417. {
  418.   try {
  419.     if (!gStringBundle) {
  420.       var bundleService = Cc[SB_CONTRACTID].getService(); 
  421.       bundleService = bundleService.QueryInterface(Ci.nsIStringBundleService);
  422.       gStringBundle = bundleService.createBundle(PLACES_BUNDLE_URI);
  423.     }
  424.  
  425.     if (gStringBundle)
  426.       return gStringBundle.GetStringFromName(name);
  427.   } catch (ex) {
  428.     LOG("Exception loading string bundle: " + ex.message);
  429.   }
  430.  
  431.   return null;
  432. }
  433.  
  434. var gLivemarkService;
  435. function LivemarkService() {
  436.  
  437.   try {
  438.     var prefs = Cc[PS_CONTRACTID].getService(Ci.nsIPrefBranch);
  439.     var livemarkRefresh = 
  440.       prefs.getIntPref("browser.bookmarks.livemark_refresh_seconds");
  441.     // Reset global expiration variable to reflect hidden pref (in ms)
  442.     // with a lower limit of 1 minute (60000 ms)
  443.     gExpiration = Math.max(livemarkRefresh * 1000, 60000);
  444.   } 
  445.   catch (ex) { }
  446.     
  447.   // [ {folderId:, folderURI:, feedURI:, loadGroup:, locked: } ];
  448.   this._livemarks = [];
  449.  
  450.   this._iconURI = gIoService.newURI(LIVEMARK_ICON_URI, null, null);
  451.   this._loading = GetString("bookmarksLivemarkLoading") || DEFAULT_LOAD_MSG;
  452.   this._observerServiceObserver =
  453.     new G_ObserverServiceObserver('xpcom-shutdown',
  454.                                   BindToObject(this._shutdown, this),
  455.                                   true /*only once*/);
  456.   new G_Alarm(BindToObject(this._fireTimer, this), LIVEMARK_TIMEOUT, 
  457.               true /* repeat */);
  458.  
  459.   // this is giving a reentrant getService warning in XPCShell. bug 194568.
  460.   this._ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService);
  461.  
  462.   var livemarks = this._ans.getItemsWithAnnotation(LMANNO_FEEDURI, {});
  463.   for (var i = 0; i < livemarks.length; i++) {
  464.     var feedURI =
  465.       gIoService.newURI(
  466.         this._ans.getItemAnnotation(livemarks[i], LMANNO_FEEDURI),
  467.         null, null
  468.       );
  469.     this._pushLivemark(livemarks[i], feedURI);
  470.   }
  471. }
  472.  
  473. LivemarkService.prototype = {
  474.  
  475.   get _bms() {
  476.     if (!this.__bms)
  477.       this.__bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService);
  478.     return this.__bms;
  479.   },
  480.  
  481.   get _history() {
  482.     if (!this.__history)
  483.       this.__history = Cc[NH_CONTRACTID].getService(Ci.nsINavHistoryService);
  484.     return this.__history;
  485.   },
  486.  
  487.   // returns new length of _livemarks
  488.   _pushLivemark: function LS__pushLivemark(folderId, feedURI) {
  489.     return this._livemarks.push({folderId: folderId, feedURI: feedURI});
  490.   },
  491.  
  492.   _getLivemarkIndex: function LS__getLivemarkIndex(folderId) {
  493.     for (var i=0; i < this._livemarks.length; ++i) {
  494.       if (this._livemarks[i].folderId == folderId)
  495.         return i;
  496.     }
  497.     throw Cr.NS_ERROR_INVALID_ARG;
  498.   },
  499.  
  500.   _shutdown: function LS__shutdown() {
  501.     for (var livemark in this._livemarks) {
  502.       if (livemark.loadGroup) 
  503.         livemark.loadGroup.cancel(Cr.NS_BINDING_ABORTED);
  504.     }
  505.   },
  506.  
  507.   _fireTimer: function LS__fireTimer() {
  508.     for (var i=0; i < this._livemarks.length; ++i) {
  509.       this._updateLivemarkChildren(i, false);
  510.     }
  511.   },
  512.  
  513.   deleteLivemarkChildren: function LS_deleteLivemarkChildren(folderId) {
  514.     this._bms.removeFolderChildren(folderId);
  515.   },
  516.  
  517.   insertLivemarkLoadingItem: function LS_insertLivemarkLoading(bms, folderId) {
  518.     var loadingURI = gIoService.newURI("about:livemark-loading", null, null);
  519.     var id = bms.insertBookmark(folderId, loadingURI, bms.DEFAULT_INDEX,
  520.                                 this._loading);
  521.   },
  522.  
  523.   _updateLivemarkChildren:
  524.   function LS__updateLivemarkChildren(index, forceUpdate) {
  525.     if (this._livemarks[index].locked)
  526.       return;
  527.     
  528.     var livemark = this._livemarks[index];
  529.     livemark.locked = true;
  530.     try {
  531.       // Check the TTL/expiration on this.  If there isn't one,
  532.       // then we assume it's never been loaded.  We perform this
  533.       // check even when the update is being forced, in case the
  534.       // livemark has somehow never been loaded.
  535.       var exprTime = this._ans.getPageAnnotation(livemark.feedURI,
  536.                                                  LMANNO_EXPIRATION);
  537.       if (!forceUpdate && exprTime > Date.now()) {
  538.         // no need to refresh
  539.         livemark.locked = false;
  540.         return;
  541.       }
  542.     } 
  543.     catch (ex) {
  544.       // This livemark has never been loaded, since it has no expire time.
  545.       this.insertLivemarkLoadingItem(this._bms, livemark.folderId);
  546.     }
  547.  
  548.     var loadgroup;
  549.     try {
  550.       // Create a load group for the request.  This will allow us to
  551.       // automatically keep track of redirects, so we can always
  552.       // cancel the channel.
  553.       loadgroup = Cc[LG_CONTRACTID].createInstance(Ci.nsILoadGroup);
  554.       var uriChannel = gIoService.newChannel(livemark.feedURI.spec, null, null);
  555.       uriChannel.loadGroup = loadgroup;
  556.       uriChannel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND | 
  557.                               Ci.nsIRequest.VALIDATE_ALWAYS;
  558.       var httpChannel = uriChannel.QueryInterface(Ci.nsIHttpChannel);
  559.       httpChannel.requestMethod = "GET";
  560.       httpChannel.setRequestHeader("X-Moz", "livebookmarks", false);
  561.  
  562.       // Stream the result to the feed parser with this listener
  563.       var listener = new LivemarkLoadListener(livemark);
  564.       httpChannel.asyncOpen(listener, null);
  565.     }
  566.     catch (ex) {
  567.       livemark.locked = false;
  568.       LOG("exception: " + ex);
  569.       throw ex;
  570.     }
  571.     livemark.loadGroup = loadgroup;
  572.   },
  573.  
  574.   createLivemark: function LS_createLivemark(folder, name, siteURI,
  575.                                              feedURI, index) {
  576.     // Don't add livemarks to livemarks
  577.     if (this.isLivemark(folder))
  578.       throw Cr.NS_ERROR_INVALID_ARG;
  579.     var livemarkID = this._createFolder(this._bms, folder, name, siteURI,
  580.                                         feedURI, index);
  581.   
  582.     // kick off http fetch
  583.     this._updateLivemarkChildren(
  584.       this._pushLivemark(livemarkID, feedURI) - 1,
  585.       false
  586.     );
  587.  
  588.     return livemarkID;
  589.   },
  590.  
  591.   createLivemarkFolderOnly:
  592.   function LS_createLivemarkFolderOnly(bms, folder, name, siteURI,
  593.                                        feedURI, index) {
  594.     var livemarkID = this._createFolder(bms, folder, name, siteURI, feedURI,
  595.                                         index);
  596.     this.insertLivemarkLoadingItem(bms, livemarkID);
  597.     this._pushLivemark(livemarkID, feedURI);
  598.     
  599.     return livemarkID;
  600.   },
  601.  
  602.   _createFolder:
  603.   function LS__createFolder(bms, folder, name, siteURI, feedURI, index) {
  604.     var livemarkID = bms.createContainer(folder, name, LS_CONTRACTID, index);
  605.  
  606.     // Add an annotation to map the folder URI to the livemark feed URI
  607.     this._ans.setItemAnnotation(livemarkID, LMANNO_FEEDURI, feedURI.spec, 0,
  608.                                 this._ans.EXPIRE_NEVER);
  609.     // Set the favicon
  610.     var faviconService = Cc[FAV_CONTRACTID].getService(Ci.nsIFaviconService);
  611.     var livemarkURI = bms.getFolderURI(livemarkID);
  612.     faviconService.setFaviconUrlForPage(livemarkURI, this._iconURI);
  613.  
  614.     if (siteURI) {
  615.       // Add an annotation to map the folder URI to the livemark site URI
  616.       this._ans.setItemAnnotation(livemarkID, LMANNO_SITEURI, siteURI.spec,
  617.                                   0, this._ans.EXPIRE_NEVER);
  618.     }
  619.  
  620.     return livemarkID;
  621.   },
  622.  
  623.   isLivemark: function LS_isLivemark(folder) {
  624.     return this._ans.itemHasAnnotation(folder, LMANNO_FEEDURI);
  625.   },
  626.  
  627.   _ensureLivemark: function LS__ensureLivemark(container) {
  628.     if (!this.isLivemark(container)) 
  629.       throw Cr.NS_ERROR_INVALID_ARG;
  630.   },
  631.  
  632.   /**
  633.   * n.b. -- the body of this method is duplicated in 
  634.   *         /browser/components/places/content/toolbar.xml
  635.   *         to avoid instantiating the livemark service on
  636.   *         startup.
  637.   */
  638.   getSiteURI: function LS_getSiteURI(container) {
  639.     try {
  640.       this._ensureLivemark(container);
  641.       
  642.       // getItemAnnotation() can throw if there is no annotation
  643.       var siteURIString =
  644.         this._ans.getItemAnnotation(container, LMANNO_SITEURI);
  645.  
  646.       return gIoService.newURI(siteURIString, null, null);
  647.     }
  648.     catch (ex) {
  649.       // temporary logging, for bug #381894
  650.       LOG("getSiteURI failed: " + ex);
  651.       LOG("siteURIString: " + siteURIString);
  652.     }
  653.     return null;
  654.   },
  655.  
  656.   setSiteURI: function LS_setSiteURI(container, siteURI) {
  657.     this._ensureLivemark(container);
  658.     
  659.     if (!siteURI) {
  660.       this._ans.removeItemAnnotation(container, LMANNO_SITEURI);
  661.       return;
  662.     }
  663.  
  664.     this._ans.setItemAnnotation(container, LMANNO_SITEURI, siteURI.spec,
  665.                                       0, this._ans.EXPIRE_NEVER);
  666.   },
  667.  
  668.   getFeedURI: function LS_getFeedURI(container) {
  669.     try {
  670.       // getItemAnnotation() can throw if there is no annotation
  671.       var feedURIString = this._ans.getItemAnnotation(container,
  672.                                                       LMANNO_FEEDURI);
  673.        
  674.       return gIoService.newURI(feedURIString, null, null);
  675.     }
  676.     catch (ex) {
  677.       // temporary logging, for bug #381894
  678.       LOG("getFeedURI failed: " + ex);
  679.       LOG("feedURIString: " + feedURIString);
  680.     }
  681.     return null;
  682.   },
  683.  
  684.   setFeedURI: function LS_setFeedURI(container, feedURI) {
  685.     if (!feedURI)
  686.       throw Cr.NS_ERROR_INVALID_ARG;
  687.  
  688.     this._ans.setItemAnnotation(container, LMANNO_FEEDURI, feedURI.spec, 0,
  689.                                 this._ans.EXPIRE_NEVER);
  690.  
  691.     // now update our internal table
  692.     var livemarkIndex = this._getLivemarkIndex(container);  
  693.     this._livemarks[livemarkIndex].feedURI = feedURI;
  694.   },
  695.  
  696.   reloadAllLivemarks: function LS_reloadAllLivemarks() {
  697.     for (var i = 0; i < this._livemarks.length; ++i) {
  698.       this._updateLivemarkChildren(i, true);
  699.     }
  700.   },
  701.  
  702.   reloadLivemarkFolder: function LS_reloadLivemarkFolder(folderID) {
  703.     var livemarkIndex = this._getLivemarkIndex(folderID);  
  704.     this._updateLivemarkChildren(livemarkIndex, true);
  705.   },
  706.  
  707.   // nsIRemoteContainer
  708.   onContainerRemoving: function LS_onContainerRemoving(container) {
  709.     var livemarkIndex = this._getLivemarkIndex(container);
  710.     var livemark = this._livemarks[livemarkIndex];
  711.  
  712.     var stillInUse = false;
  713.     stillInUse = this._livemarks.some(
  714.                  function(mark) { return mark.feedURI.equals(livemark.feedURI) } 
  715.                  );
  716.     if (!stillInUse) {
  717.       // ??? the code in the C++ had "livemark_expiration" as
  718.       // the second arg... that must be wrong
  719.       this._ans.removePageAnnotation(livemark.feedURI, LMANNO_EXPIRATION);
  720.     }
  721.  
  722.     if (livemark.loadGroup) 
  723.       livemark.loadGroup.cancel(Cr.NS_BINDING_ABORTED);
  724.     this._livemarks.splice(livemarkIndex, 1);
  725.     this.deleteLivemarkChildren(container);
  726.   },
  727.  
  728.   onContainerMoved:
  729.   function LS_onContainerMoved(container, newFolder, newIndex) { },
  730.  
  731.   childrenReadOnly: true,
  732.  
  733.   createInstance: function LS_createInstance(outer, iid) {
  734.     if (outer != null)
  735.       throw Cr.NS_ERROR_NO_AGGREGATION;
  736.     return this.QueryInterface(iid);
  737.   },
  738.   
  739.   QueryInterface: function LS_QueryInterface(iid) {
  740.     if (iid.equals(Ci.nsILivemarkService) ||
  741.         iid.equals(Ci.nsIRemoteContainer) ||
  742.         iid.equals(Ci.nsIFactory) ||
  743.         iid.equals(Ci.nsISupports))
  744.       return this;
  745.     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  746.   },
  747. };
  748.  
  749. function LivemarkLoadListener(livemark) {
  750.   this._livemark = livemark;
  751.   this._processor = null;
  752.   this._isAborted = false;
  753.   this._ttl = gExpiration;
  754.   this._ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService);
  755. }
  756.  
  757. LivemarkLoadListener.prototype = {
  758.  
  759.   get _failed() {
  760.     return GetString("bookmarksLivemarkFailed") || DEFAULT_FAIL_MSG;
  761.   },
  762.  
  763.   abort: function LLL_abort() {
  764.     this._isAborted = true;
  765.   },
  766.  
  767.   get _bms() {
  768.     if (!this.__bms)
  769.       this.__bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService);
  770.     return this.__bms;
  771.   },
  772.  
  773.   get _history() {
  774.     if (!this.__history)
  775.       this.__history = Cc[NH_CONTRACTID].getService(Ci.nsINavHistoryService);
  776.     return this.__history;
  777.   },
  778.  
  779.   // called back from handleResult
  780.   runBatched: function LLL_runBatched(aUserData) {
  781.     var result = aUserData.QueryInterface(Ci.nsIFeedResult);
  782.  
  783.     // We need this to make sure the item links are safe
  784.     var secMan = Cc[SEC_CONTRACTID].getService(Ci.nsIScriptSecurityManager);
  785.       
  786.     // Clear out any child nodes of the livemark folder, since
  787.     // they're about to be replaced.
  788.     var lmService = Cc[LS_CONTRACTID].getService(Ci.nsILivemarkService);
  789.     this.deleteLivemarkChildren(this._livemark.folderId);
  790.  
  791.     // Enforce well-formedness because the existing code does
  792.     if (!result || !result.doc || result.bozo) {
  793.       this.insertLivemarkFailedItem(this._livemark.folderId);
  794.       this._ttl = gExpiration;
  795.       throw Cr.NS_ERROR_FAILURE;
  796.     }
  797.  
  798.     var title, href, entry;
  799.     var feed = result.doc.QueryInterface(Ci.nsIFeed);
  800.     // Loop through and check for a link and a title
  801.     // as the old code did
  802.     for (var i = 0; i < feed.items.length; ++i) {
  803.       entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
  804.       if (entry.title)
  805.         title = entry.title.plainText();
  806.       else if (entry.updated)
  807.         title = entry.updated;
  808.  
  809.       if (entry.link) {
  810.         try {
  811.           secMan.checkLoadURIStr(this._livemark.feedURI.spec, entry.link.spec,
  812.                                  SEC_FLAGS);
  813.           href = entry.link;
  814.         }
  815.         catch (ex) { }
  816.       }
  817.  
  818.       if (href && title)
  819.         this.insertLivemarkChild(this._livemark.folderId, href, title);
  820.     }
  821.   },
  822.  
  823.   /**
  824.    * See nsIFeedResultListener.idl
  825.    */
  826.   handleResult: function LLL_handleResult(result) {
  827.     if (this._isAborted) {
  828.       this._livemark.locked = false;
  829.       return;
  830.     }
  831.     try {
  832.       // The actual work is done in runBatched, see above.
  833.       this._bms.runInBatchMode(this, result);
  834.     }
  835.     finally {
  836.       this._processor.listener = null;
  837.       this._processor = null;
  838.       this._livemark.locked = false;
  839.     }
  840.   },
  841.   
  842.   deleteLivemarkChildren: LivemarkService.prototype.deleteLivemarkChildren,
  843.  
  844.   insertLivemarkFailedItem: function LS_insertLivemarkFailed(folderId) {
  845.     var failedURI = gIoService.newURI("about:livemark-failed", null, null);
  846.     var id = this._bms.insertBookmark(folderId, failedURI, this._bms.DEFAULT_INDEX,
  847.                                       this._failed);
  848.   },
  849.  
  850.   insertLivemarkChild:
  851.   function LS_insertLivemarkChild(folderId, uri, title) {
  852.     var id = this._bms.insertBookmark(folderId, uri, this._bms.DEFAULT_INDEX,
  853.                                       title);
  854.     this._ans.setItemAnnotation(id, LMANNO_BMANNO, uri.spec, 0,
  855.                                 this._ans.EXPIRE_NEVER);
  856.   },
  857.  
  858.   /**
  859.    * See nsIStreamListener.idl
  860.    */
  861.   onDataAvailable: function LLL_onDataAvailable(request, context, inputStream, 
  862.                                                 sourceOffset, count) {
  863.     this._processor.onDataAvailable(request, context, inputStream,
  864.                                     sourceOffset, count);
  865.   },
  866.   
  867.   /**
  868.    * See nsIRequestObserver.idl
  869.    */
  870.   onStartRequest: function LLL_onStartRequest(request, context) {
  871.     if (this._isAborted)
  872.       throw Cr.NS_ERROR_UNEXPECTED;
  873.  
  874.     var channel = request.QueryInterface(Ci.nsIChannel);
  875.  
  876.     // Parse feed data as it comes in
  877.     this._processor = Cc[FP_CONTRACTID].createInstance(Ci.nsIFeedProcessor);
  878.     this._processor.listener = this;
  879.     this._processor.parseAsync(null, channel.URI);
  880.     
  881.     this._processor.onStartRequest(request, context);
  882.   },
  883.   
  884.   /**
  885.    * See nsIRequestObserver.idl
  886.    */
  887.   onStopRequest: function LLL_onStopRequest(request, context, status) {
  888.     if (!Components.isSuccessCode(status)) {
  889.       // Something went wrong; try to load again in a bit
  890.       this._setResourceTTL(ERROR_EXPIRATION);
  891.       this._isAborted = true;
  892.       return;
  893.     }
  894.     // Set an expiration on the livemark, for reloading the data
  895.     try { 
  896.       this._processor.onStopRequest(request, context, status);
  897.  
  898.       // Calculate a new ttl
  899.       var channel = request.QueryInterface(Ci.nsICachingChannel);
  900.       if (channel) {
  901.         var entryInfo = channel.cacheToken.QueryInterface(Ci.nsICacheEntryInfo);
  902.         if (entryInfo) {
  903.           // nsICacheEntryInfo returns value as seconds, 
  904.           // expiresTime stores as ms
  905.           var expiresTime = entryInfo.expirationTime * 1000;
  906.           var nowTime = Date.now();
  907.           
  908.           // note, expiresTime can be 0, see bug #383538
  909.           if (expiresTime > nowTime) {
  910.             this._setResourceTTL(Math.max((expiresTime - nowTime),
  911.                                  gExpiration));
  912.             return;
  913.           }
  914.         }
  915.       }
  916.     }
  917.     catch (ex) { }
  918.     this._setResourceTTL(this._ttl);
  919.   },
  920.  
  921.   _setResourceTTL: function LLL__setResourceTTL(milliseconds) {
  922.     var exptime = Date.now() + milliseconds;
  923.     this._ans.setPageAnnotation(this._livemark.feedURI, LMANNO_EXPIRATION,
  924.                                 exptime, 0,
  925.                                 Ci.nsIAnnotationService.EXPIRE_NEVER);
  926.   },
  927.   
  928.   /**
  929.    * See nsISupports.idl
  930.    */
  931.   QueryInterface: function LLL_QueryInterface(iid) {
  932.     if (iid.equals(Ci.nsIFeedResultListener) ||
  933.         iid.equals(Ci.nsIStreamListener) ||
  934.         iid.equals(Ci.nsIRequestObserver)||
  935.         iid.equals(Ci.nsINavHistoryBatchCallback) ||
  936.         iid.equals(Ci.nsISupports))
  937.       return this;
  938.     throw Cr.NS_ERROR_NO_INTERFACE;
  939.   },
  940. }
  941.  
  942. function GenericComponentFactory(ctor) {
  943.   this._ctor = ctor;
  944. }
  945.  
  946. GenericComponentFactory.prototype = {
  947.  
  948.   _ctor: null,
  949.  
  950.   // nsIFactory
  951.   createInstance: function(outer, iid) {
  952.     if (outer != null)
  953.       throw Cr.NS_ERROR_NO_AGGREGATION;
  954.     return (new this._ctor()).QueryInterface(iid);
  955.   },
  956.  
  957.   // nsISupports
  958.   QueryInterface: function(iid) {
  959.     if (iid.equals(Ci.nsIFactory) ||
  960.         iid.equals(Ci.nsISupports))
  961.       return this;
  962.     throw Cr.NS_ERROR_NO_INTERFACE;
  963.   },
  964.  
  965. };
  966.  
  967. var Module = {
  968.   QueryInterface: function(iid) {
  969.     if (iid.equals(Ci.nsIModule) || 
  970.         iid.equals(Ci.nsISupports))
  971.       return this;
  972.  
  973.     throw Cr.NS_ERROR_NO_INTERFACE;
  974.   },
  975.  
  976.   getClassObject: function M_getClassObject(cm, cid, iid) {
  977.     if (!iid.equals(Ci.nsIFactory))
  978.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  979.     if (cid.equals(LS_CLASSID))
  980.       return new GenericComponentFactory(LivemarkService);
  981.  
  982.     throw Cr.NS_ERROR_NO_INTERFACE;
  983.   },
  984.  
  985.   registerSelf: function(cm, file, location, type) {
  986.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  987.  
  988.     cr.registerFactoryLocation(LS_CLASSID, LS_CLASSNAME,
  989.       LS_CONTRACTID, file, location, type);    
  990.   },
  991.  
  992.   unregisterSelf: function M_unregisterSelf(cm, location, type) {
  993.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  994.     cr.unregisterFactoryLocation(LS_CLASSID, location);
  995.   },
  996.   
  997.   canUnload: function M_canUnload(cm) {
  998.     return true;
  999.   }
  1000. };
  1001.  
  1002. function NSGetModule(cm, file) {
  1003.   return Module;
  1004. }
  1005.